package ren.yugong.fileutil;


import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;

public class TxtTemplateFactory {

    private static final Map<String, TxtTemplateModel> templates = new HashMap<>();

    private TxtTemplateFactory() {

    }

    public static <T> TxtReadStream<T> createTxtReadStream(Class<T> txtTemplateCls) {
        TxtTemplateModel<T> template = getTemplate(txtTemplateCls);

        FileType fileType = template.getFileType();

        TxtReadStream<T> readStream;

        readStream = new TxtTemplateSplitReadStream<>(template);
        /*
        switch (fileType) {
            case SPLIT:
                readStream = new TxtTemplateSplitReadStream<>(template);
                break;
            default:
                readStream = new TxtTemplateLengthReadStream<>(template);
        }
         */
        return readStream;
    }

    public static <T> TxtWriteStream<T> createTxtWriteStream(Class<T> txtTemplateCls) {
        TxtTemplateModel template = getTemplate(txtTemplateCls);
        FileType fileType = template.getFileType();

        TxtWriteStream<T> writeStream;
        writeStream = new TxtTemplateSplitWriteStream<>(template);
        /*
        switch (fileType) {
            case SPLIT:
                writeStream = new TxtTemplateSplitWriteStream<>(template);
                break;
            default:
                writeStream = new TxtTemplateLengthWriteStream<>(template);
        }
         */
        return writeStream;
    }

    public static <T> TxtTemplateModel<T> getTemplate(Class<T> cls) {
        TxtTemplateModel model = templates.get(cls.getName());
        if (model != null) {
            return model;
        } else {
            model = loadModel(cls);
            templates.put(cls.getName(), model);
            return model;
        }
    }

    public static <T> TxtTemplateModel<T> loadModel(Class<T> cls)  {
        TxtTemplateModel<T> model = new TxtTemplateModel();
        TxtTemplate template = cls.getDeclaredAnnotation(TxtTemplate.class);
        if (null == template) {
            throw new TemplateNotFoundException(cls.getName() + " not found @Template");
        }

        String title = template.title();
        String columnSplit = template.columnSplit();
        TxtTemplateColumnType columnSplitType = template.columnSplitType();
        LengthType lengthType = template.lengthType();
        String lineSeparator = template.lineSeparator().getLineSeparator();
        String charset = template.charset();

        model.setTxtTemplateCls(cls);
        model.setTitle(title);
        model.setColumnSplit(columnSplit);
        model.setColumnSplitType(columnSplitType);
        model.setLengthType(lengthType);
        model.setLineSeparator(lineSeparator);
        model.setCharset(charset);

        Method[] methods = cls.getMethods();
        Map<String, Field> fields = fieldMap(cls);

        List<MethodInfo> writeMethodInfos = new ArrayList<>();
        List<MethodInfo> readMethodInfos = new ArrayList<>();

        MethodInfo methodInfo;
        for (Method method : methods) {
            String name = method.getName();

            String returnType = method.getReturnType().getName();
            int paramLength = method.getGenericParameterTypes().length;
            String s;
            String methodType = "";
            if (!name.equals("getClass") && name.startsWith("get") && paramLength  == 0 && (!"void".equals(returnType))) {
                s = name.substring(3);
                methodType = "GET";
            } else if (name.startsWith("is") && paramLength == 0 && (!"void".equals(returnType))) {
                s = name.substring(2);
                methodType = "IS";
            } else if (name.startsWith("set") && paramLength == 1) {
                s = name.substring(3);
                methodType = "SET";
            } else {
                continue;
            }
            String fieldName = s.substring(0, 1).toLowerCase() + s.substring(1);

            TxtTemplateIgnoreColumn ignore = method.getAnnotation(TxtTemplateIgnoreColumn.class);
            if (null != ignore) {
                continue;
            }

            TxtTemplateColumn annotation = method.getAnnotation(TxtTemplateColumn.class);
            if (null == annotation) {
                if (!fields.containsKey(fieldName)) {
                    throw new TemplateNotFoundColumnException(cls.getName() + " " + name + " not found @Column");
                }
                Field declaredField = fields.get(fieldName);

                ignore = declaredField.getAnnotation(TxtTemplateIgnoreColumn.class);
                if (ignore != null) {
                    continue;
                }
                annotation = declaredField.getAnnotation(TxtTemplateColumn.class);

                if (null == annotation) {
                    throw new TemplateNotFoundColumnException(cls.getName() + " " + fieldName + " not found @Column");
                }

            }
            methodInfo = new MethodInfo();

            methodInfo.setMethod(method);
            methodInfo.setOrder(annotation.order());
            methodInfo.setName(annotation.name());
            methodInfo.setFormat(annotation.format());
            methodInfo.setLength(annotation.length());
            methodInfo.setMultiple(annotation.multiple());

            switch (methodType) {
                case "SET" :
                    readMethodInfos.add(methodInfo);
                    break;
                default:
                    writeMethodInfos.add(methodInfo);
            }

        }

        Collections.sort(writeMethodInfos);
        Collections.sort(readMethodInfos);

        model.setWriteMethodInfos(writeMethodInfos);
        model.setReadMethodInfos(readMethodInfos);
        return model;
    }

    private static Map<String, Field> fieldMap(Class<?> c) {
        Set<Field> allField = allField(c);

        return fieldToMap(allField);
    }

    private static Set<Field> allField(Class<?> c) {
        Set<Field> fields = new HashSet<>();

        fields.addAll(Arrays.asList(c.getDeclaredFields()));

        c = c.getSuperclass();
        if (c != null) {
            Set<Field> allFields = allField(c);
            for (Field f : allFields) {
                if (!fields.contains(f.getName())) {
                    fields.addAll(allFields);
                }
            }
        }

        return fields;
    }

    private static Map<String, Field> fieldToMap(Set<Field> allField) {
        Map<String, Field> fieldMap = new HashMap<>();
        for (Field field : allField) {
            fieldMap.put(field.getName(), field);
        }
        return fieldMap;
    }


}
